import torch
import torch.nn as nn
import torch.nn.functional as F
from mmcv.cnn import normal_init

from mmdet.ops import arb_batched_nms
from mmdet.core import obb2hbb
from mmdet.models.builder import HEADS
from .obb_anchor_head import OBBAnchorHead
from ..rpn_test_mixin import RPNTestMixin
from mmdet.core import (anchor_inside_flags, build_anchor_generator,
                        build_assigner, build_bbox_coder, build_sampler,
                        force_fp32, images_to_levels, multi_apply, unmap,
                        get_bbox_type, get_bbox_dim, bbox2type, multiclass_arb_nms)
from mmdet.models.losses.utils import weight_reduce_loss
from einops import rearrange
from mmdet.models.builder import HEADS, build_loss
from math import pi


@HEADS.register_module()
class AngleClsRPNHead(OBBAnchorHead, RPNTestMixin):
    """RPN head.

    Args:
        in_channels (int): Number of channels in the input feature map.
    """  # noqa: W605

    def __init__(self, in_channels, onehot_dim=60, **kwargs):
        self.onehot_dim = onehot_dim
        super(AngleClsRPNHead, self).__init__(
            1,
            in_channels,
            bbox_type='obb',
            reg_dim=4,
            background_label=0,
            **kwargs)
        self._get_std_w()

    def _get_std_w(self):
        temp = torch.arange(0, self.onehot_dim)
        std_w = torch.abs(torch.sin(temp * pi / self.onehot_dim - pi / 2)) * 10
        std_w[self.onehot_dim // 2] = std_w[self.onehot_dim // 2 + 1]
        self.std_w = std_w.to('cuda')

    def _init_layers(self):
        """Initialize layers of the head."""
        self.rpn_conv = nn.Conv2d(
            self.in_channels, self.feat_channels, 3, padding=1)
        self.rpn_cls = nn.Conv2d(self.feat_channels,
                                 self.num_anchors * self.cls_out_channels, 1)
        # self.angle_cls = nn.Conv2d(self.feat_channels, self.num_anchors * self.onehot_dim, 1);  # 我的角度分类头
        self.angle_cls = nn.Conv2d(self.feat_channels, self.num_anchors * self.onehot_dim, 1);  # 我的角度分类头
        self.rpn_reg = nn.Conv2d(self.feat_channels, self.num_anchors * 4, 1)

    def init_weights(self):
        """Initialize weights of the head."""
        normal_init(self.rpn_conv, std=0.01)
        normal_init(self.rpn_cls, std=0.01)
        normal_init(self.rpn_reg, std=0.01)
        normal_init(self.angle_cls, std=0.01)  # 我的角度分类头，初始值应该是很小的一个值，从而能够稳定的训练

    def forward_single(self, x):
        """Forward feature map of a single scale level."""
        x = self.rpn_conv(x)
        x = F.relu(x, inplace=True)
        rpn_cls_score = self.rpn_cls(x)
        rpn_bbox_pred = self.rpn_reg(x)
        rpn_angle_pred = self.angle_cls(x)
        return rpn_cls_score, rpn_bbox_pred, rpn_angle_pred  # 在考虑将其直接融入分类头

    def _get_targets_single(self,
                            flat_anchors,
                            valid_flags,
                            gt_bboxes,
                            gt_bboxes_ignore,
                            gt_labels,
                            img_meta,
                            label_channels=1,
                            unmap_outputs=True):
        """Compute regression and classification targets for anchors in
            a single image.在单个图像中计算分类和回归目标

        Args:
            flat_anchors (Tensor): Multi-level anchors of the image, which are
                concatenated into a single tensor of shape (num_anchors ,4)
            valid_flags (Tensor): Multi level valid flags of the image,
                which are concatenated into a single tensor of
                    shape (num_anchors,).
            gt_bboxes (Tensor): Ground truth bboxes of the image,
                shape (num_gts, 4).
            img_meta (dict): Meta info of the image.
            gt_bboxes_ignore (Tensor): Ground truth bboxes to be
                ignored, shape (num_ignored_gts, 4).
            img_meta (dict): Meta info of the image.
            gt_labels (Tensor): Ground truth labels of each box,
                shape (num_gts,).
            label_channels (int): Channel of label.
            unmap_outputs (bool): Whether to map outputs back to the original
                set of anchors.

        Returns:
            tuple:
                labels_list (list[Tensor]): Labels of each level
                label_weights_list (list[Tensor]): Label weights of each level
                bbox_targets_list (list[Tensor]): BBox targets of each level
                bbox_weights_list (list[Tensor]): BBox weights of each level
                num_total_pos (int): Number of positive samples in all images
                num_total_neg (int): Number of negative samples in all images
        """
        inside_flags = anchor_inside_flags(flat_anchors, valid_flags,
                                           img_meta['img_shape'][:2],
                                           self.train_cfg.allowed_border)
        if not inside_flags.any():
            return (None,) * 6
        # assign gt and sample anchors
        anchors = flat_anchors[inside_flags, :]

        anchor_bbox_type = get_bbox_type(anchors)
        gt_bbox_type = get_bbox_type(gt_bboxes)
        target_bboxes = bbox2type(gt_bboxes, anchor_bbox_type)
        target_bboxes_ignore = None if gt_bboxes_ignore is None else \
            bbox2type(gt_bboxes_ignore, anchor_bbox_type)
        assign_result = self.assigner.assign(
            anchors, target_bboxes, target_bboxes_ignore,
            None if self.sampling else gt_labels)
        sampling_result = self.sampler.sample(assign_result, anchors,
                                              target_bboxes)
        if anchor_bbox_type != gt_bbox_type:
            if gt_bboxes.numel() == 0:
                sampling_result.pos_gt_bboxes = torch.empty_like(
                    gt_bboxes).view(-1, get_bbox_dim(gt_bbox_type))
            else:
                sampling_result.pos_gt_bboxes = \
                    gt_bboxes[sampling_result.pos_assigned_gt_inds, :]

        num_valid_anchors = anchors.shape[0]
        target_dim = self.reg_dim if not self.reg_decoded_bbox else \
            get_bbox_dim(self.bbox_type)
        target_dim += 2  # target维度是6
        bbox_targets = anchors.new_zeros((anchors.size(0), target_dim))
        bbox_weights = anchors.new_zeros((anchors.size(0), target_dim))
        labels = anchors.new_full((num_valid_anchors,),
                                  self.background_label,
                                  dtype=torch.long)
        label_weights = anchors.new_zeros(num_valid_anchors, dtype=torch.float)

        pos_inds = sampling_result.pos_inds
        neg_inds = sampling_result.neg_inds
        if len(pos_inds) > 0:
            if not self.reg_decoded_bbox:
                pos_bbox_targets = self.bbox_coder.encode(  # 这里会生成6个。我的分类需要用到第六个
                    sampling_result.pos_bboxes, sampling_result.pos_gt_bboxes)
            else:
                pos_bbox_targets = sampling_result.pos_gt_bboxes
            bbox_targets[pos_inds, :] = pos_bbox_targets
            bbox_weights[pos_inds, :] = 1.0  # bbox_weight只对正样本计算损失
            if gt_labels is None:
                # only rpn gives gt_labels as None, this time FG is 1 # FG被设置为1,因为是在rpn阶段
                labels[pos_inds] = 1
            else:
                labels[pos_inds] = gt_labels[
                    sampling_result.pos_assigned_gt_inds]
            if self.train_cfg.pos_weight <= 0:
                label_weights[pos_inds] = 1.0
            else:
                label_weights[pos_inds] = self.train_cfg.pos_weight
        if len(neg_inds) > 0:
            label_weights[neg_inds] = 1.0

        # map up to original set of anchors
        if unmap_outputs:
            num_total_anchors = flat_anchors.size(0)
            labels = unmap(
                labels,
                num_total_anchors,
                inside_flags,
                fill=self.background_label)  # fill bg label
            label_weights = unmap(label_weights, num_total_anchors,
                                  inside_flags)
            bbox_targets = unmap(bbox_targets, num_total_anchors, inside_flags)
            bbox_weights = unmap(bbox_weights, num_total_anchors, inside_flags)

        return (labels, label_weights, bbox_targets, bbox_weights, pos_inds,
                neg_inds, sampling_result)

    def _angle_cls_loss_calculate(self, angle_pred, bbox_weights, num_total_samples, angle_targets):
        valid = bbox_weights > 0
        if not torch.any(valid):
            return torch.tensor(0.0)
        angle_pred = rearrange(angle_pred, 'b (c n_cls) h w -> (b h w c) n_cls',
                               n_cls=self.onehot_dim)  # (BHWC,onehot_dim)
        angle_pred = angle_pred[valid]
        angle_targets = rearrange(angle_targets, 'b d -> (b d)')  # angle_targets:(b,nt) #nt表示这一个尺度所有的bbox
        angle_targets = angle_targets[valid]
        angle_targets = ((angle_targets + pi / 2) / pi * self.onehot_dim).long()  # 得到分类角度目标

        angle_pred_prob = torch.softmax(angle_pred, -1)  # 得到概率
        # angle_weight = rearrange(angle_weight, 'b n -> (b n)')
        # w = torch.abs(torch.sin(
        #     angle_weight * (angle_targets - ((angle_pred.argmax(-1) * pi) / self.onehot_dim - pi / 2))))  # 未完待续
        # w = [torch.roll]
        # print(angle_targets.shape, angle_targets, angle_targets.max(),angle_targets.min())
        # print(angle_pred_prob.shape, angle_targets.shape, angle_weight.shape)
        # print('*' * 100)
        # print("w uniuqe", w.unique(), "\nangle_targets_unique", angle_targets.unique())
        # print(w)
        angle_target_onehot = F.one_hot(angle_targets,
                                        num_classes=self.onehot_dim).float()  # target_onehot:(N,onehot_dim)
        dynamic_w = torch.stack([torch.roll(self.std_w, (t - self.onehot_dim // 2,)) for t in angle_targets], 0)
        # print(dynamic_w.shape)

        loss_angle = F.mse_loss(angle_pred_prob, angle_target_onehot, reduction='none') * dynamic_w  # 这里并没有使用w
        loss_angle = loss_angle.sum(-1)
        # print(dynamic_w[0],loss_angle[0])
        # print(loss_angle.shape)
        # loss_angle = F.cross_entropy(angle_pred_prob, angle_targets, reduction='none')  # 这里并没有使用w
        # loss_angle = weight_reduce_loss(loss_angle, weight=bbox_weights[:, 0], avg_factor=num_total_samples)
        loss_angle = weight_reduce_loss(loss_angle, avg_factor=num_total_samples)
        # print("dynamic w:", dynamic_w, "loss:", loss_angle)
        # index = torch.where(angle_targets != 30)[0]
        # if index.numel() > 0:
        #     index = index[0]
        #     print("*" * 100)
        #     print('target_angle:', angle_targets[index], 'pred_angle:', angle_pred.argmax(-1)[index], 'angle_weight:',
        #           angle_weight[index], 'w:',
        #           'loss:',
        #           loss_angle[index])
        # print('*' * 100)
        # print(loss_angle)
        # print("angle_prob", angle_pred_prob.shape, angle_pred.max(), angle_pred.argmax(-1))
        # print('*' * 100)
        return loss_angle
        pass

    def loss_single(self, cls_score, bbox_pred, angle_pred, anchors, labels, label_weights,
                    bbox_targets, bbox_weights, num_total_samples):
        """Compute loss of a single scale level.

        Args:
            cls_score (Tensor): Box scores for each scale level
                Has shape (N, num_anchors * num_classes, H, W).
            bbox_pred (Tensor): Box energies / deltas for each scale
                level with shape (N, num_anchors * 4, H, W).
            anchors (Tensor): Box reference for each scale level with shape
                (N, num_total_anchors, 4).
            labels (Tensor): Labels of each anchors with shape
                (N, num_total_anchors).
            label_weights (Tensor): Label weights of each anchor with shape
                (N, num_total_anchors)
            bbox_targets (Tensor): BBox regression targets of each anchor wight
                shape (N, num_total_anchors, 4).
            bbox_weights (Tensor): BBox regression loss weights of each anchor
                with shape (N, num_total_anchors, 4).
            num_total_samples (int): If sampling, num total samples equal to
                the number of total anchors; Otherwise, it is the number of
                positive anchors.

        Returns:
            dict[str, Tensor]: A dictionary of loss components.
        """
        # classification loss
        labels = labels.reshape(-1)
        label_weights = label_weights.reshape(-1)
        cls_score = cls_score.permute(0, 2, 3,
                                      1).reshape(-1, self.cls_out_channels)  # (B,n_base_anchors*1,H,W) # 因为是rpn不区分类别
        loss_cls = self.loss_cls(
            cls_score, labels, label_weights, avg_factor=num_total_samples)
        # regression loss
        target_dim = self.reg_dim if not self.reg_decoded_bbox else \
            get_bbox_dim(self.bbox_type)
        bbox_targets, angle_targets, angle_weight = bbox_targets[..., 0:4], bbox_targets[..., 4], bbox_targets[
            # bbox_targets(B,num_total_anchors(这个尺度所有的),4) angle_targets:(B,nt)
            ..., 5]
        bbox_targets = bbox_targets.reshape(-1, target_dim)  # bbox_targets:(N,n_total_anchors,4)->(N*n_total_anchors,4)
        bbox_weights = bbox_weights[..., 0:4].reshape(-1, target_dim)  # (B,
        bbox_pred = bbox_pred.permute(0, 2, 3, 1).reshape(-1, self.reg_dim)  # (B,nt*4,H,W)->(BHW*nt,4)
        if self.reg_decoded_bbox:
            anchor_dim = anchors.size(-1)
            anchors = anchors.reshape(-1, anchor_dim)
            bbox_pred = self.bbox_coder.decode(anchors, bbox_pred)
        # print(bbox_pred.size(), bbox_targets.size(), bbox_weights.size())
        loss_bbox = self.loss_bbox(
            bbox_pred,
            bbox_targets,
            bbox_weights,
            avg_factor=num_total_samples)
        # print("first_unique",angle_targets.unique())
        # angle cls 这里才是角度损失的计算
        loss_angle = self._angle_cls_loss_calculate(angle_pred, bbox_weights[..., 0],
                                                    num_total_samples=num_total_samples, angle_targets=angle_targets)
        return loss_cls, loss_bbox, loss_angle

    @force_fp32(apply_to=('cls_scores', 'bbox_preds', 'angle_preds'))
    def loss(self,
             cls_scores,
             bbox_preds,
             angle_preds,
             gt_bboxes,
             img_metas,
             gt_bboxes_ignore=None):
        """Compute losses of the head.

        Args:
            cls_scores (list[Tensor]): Box scores for each scale level
                Has shape (N, num_anchors * num_classes, H, W)
            bbox_preds (list[Tensor]): Box energies / deltas for each scale
                level with shape (N, num_anchors * 4, H, W)
            gt_bboxes (list[Tensor]): Ground truth bboxes for each image with
                shape (num_gts, 4) in [tl_x, tl_y, br_x, br_y] format.
            gt_labels (list[Tensor]): class indices corresponding to each box
            img_metas (list[dict]): Meta information of each image, e.g.,
                image size, scaling factor, etc.
            gt_bboxes_ignore (None | list[Tensor]): specify which bounding
                boxes can be ignored when computing the loss. Default: None

        Returns:
            dict[str, Tensor]: A dictionary of loss components.
        """
        featmap_sizes = [featmap.size()[-2:] for featmap in cls_scores]
        assert len(featmap_sizes) == self.anchor_generator.num_levels

        device = cls_scores[0].device
        # gt_bboxes  # B*num_gts*5
        anchor_list, valid_flag_list = self.get_anchors(
            # 根据特征图尺寸s和图像元信息得到 anchor_list:List: n_imgs * n_levels * (n*k,4)  valid_flag_list:List:n_imgs * (n*k,)
            featmap_sizes, img_metas, device=device)
        label_channels = self.cls_out_channels if self.use_sigmoid_cls else 1  # label_channel是1
        cls_reg_targets = self.get_targets(
            anchor_list,
            valid_flag_list,
            gt_bboxes,
            img_metas,
            gt_bboxes_ignore_list=gt_bboxes_ignore,
            gt_labels_list=None,
            label_channels=label_channels)
        if cls_reg_targets is None:
            return None
        (labels_list, label_weights_list, bbox_targets_list, bbox_weights_list,
         num_total_pos, num_total_neg) = cls_reg_targets
        num_total_samples = (
            num_total_pos + num_total_neg if self.sampling else num_total_pos)

        # anchor number of multi levels
        num_level_anchors = [anchors.size(0) for anchors in anchor_list[0]]
        # concat all level anchors and flags to a single tensor
        concat_anchor_list = []
        for i in range(len(anchor_list)):
            concat_anchor_list.append(torch.cat(anchor_list[i]))
        all_anchor_list = images_to_levels(concat_anchor_list,
                                           num_level_anchors)
        losses_cls, losses_bbox, loss_angle = multi_apply(
            self.loss_single,
            cls_scores,
            bbox_preds,
            angle_preds,
            all_anchor_list,
            labels_list,
            label_weights_list,
            bbox_targets_list,
            bbox_weights_list,
            num_total_samples=num_total_samples)
        return dict(rpn_loss_cls=losses_cls, rpn_loss_bbox=losses_bbox, rpn_loss_angle=loss_angle)

    @force_fp32(apply_to=('cls_scores', 'bbox_preds', 'angle_preds'))
    def get_bboxes(self,
                   cls_scores,
                   bbox_preds,
                   angle_preds,
                   img_metas,
                   cfg=None,
                   rescale=False):
        """Transform network output for a batch into bbox predictions.

        Args:
            cls_scores (list[Tensor]): Box scores for each scale level
                Has shape (N, num_anchors * num_classes, H, W)
            bbox_preds (list[Tensor]): Box energies / deltas for each scale
                level with shape (N, num_anchors * 4, H, W)
            img_metas (list[dict]): Meta information of each image, e.g.,
                image size, scaling factor, etc.
            cfg (mmcv.Config | None): Test / postprocessing configuration,
                if None, test_cfg would be used
            rescale (bool): If True, return boxes in original image space.
                Default: False.

        Returns:
            list[tuple[Tensor, Tensor]]: Each item in result_list is 2-tuple.
                The first item is an (n, 5) tensor, where the first 4 columns
                are bounding box positions (tl_x, tl_y, br_x, br_y) and the
                5-th column is a score between 0 and 1. The second item is a
                (n,) tensor where each item is the predicted class labelof the
                corresponding box.

        Example:
            >>> import mmcv
            >>> self = AnchorHead(
            >>>     num_classes=9,
            >>>     in_channels=1,
            >>>     anchor_generator=dict(
            >>>         type='AnchorGenerator',
            >>>         scales=[8],
            >>>         ratios=[0.5, 1.0, 2.0],
            >>>         strides=[4,]))
            >>> img_metas = [{'img_shape': (32, 32, 3), 'scale_factor': 1}]
            >>> cfg = mmcv.Config(dict(
            >>>     score_thr=0.00,
            >>>     nms=dict(type='nms', iou_thr=1.0),
            >>>     max_per_img=10))
            >>> feat = torch.rand(1, 1, 3, 3)
            >>> cls_score, bbox_pred = self.forward_single(feat)
            >>> # note the input lists are over different levels, not images
            >>> cls_scores, bbox_preds = [cls_score], [bbox_pred]
            >>> result_list = self.get_bboxes(cls_scores, bbox_preds,
            >>>                               img_metas, cfg)
            >>> det_bboxes, det_labels = result_list[0]
            >>> assert len(result_list) == 1
            >>> assert det_bboxes.shape[1] == 5
            >>> assert len(det_bboxes) == len(det_labels) == cfg.max_per_img
        """
        assert len(cls_scores) == len(bbox_preds)
        num_levels = len(cls_scores)

        device = cls_scores[0].device
        featmap_sizes = [cls_scores[i].shape[-2:] for i in range(num_levels)]
        mlvl_anchors = self.anchor_generator.grid_anchors(
            featmap_sizes, device=device)

        result_list = []
        for img_id in range(len(img_metas)):
            cls_score_list = [  # 按照图像进行分割 List:n_feats * (N,n_base_anchors,Hi,Wi)
                cls_scores[i][img_id].detach() for i in range(num_levels)
            ]
            bbox_pred_list = [
                bbox_preds[i][img_id].detach() for i in range(num_levels)
            ]
            angle_pred_list = [
                angle_preds[i][img_id].detach() for i in range(num_levels)
            ]
            img_shape = img_metas[img_id]['img_shape']
            scale_factor = img_metas[img_id]['scale_factor']
            proposals = self._get_bboxes_single(cls_score_list, bbox_pred_list, angle_pred_list,
                                                mlvl_anchors, img_shape,
                                                scale_factor, cfg, rescale)
            result_list.append(proposals)
        return result_list

    def _get_bboxes_single(self,
                           cls_scores,
                           bbox_preds,
                           angle_preds,
                           mlvl_anchors,
                           img_shape,
                           scale_factor,
                           cfg,
                           rescale=False):
        """Transform outputs for a single batch item into bbox predictions. 对单张图像获得bbox

        Args:
            cls_scores (list[Tensor]): Box scores for each scale level
                Has shape (num_anchors * num_classes, H, W).
            bbox_preds (list[Tensor]): Box energies / deltas for each scale
                level with shape (num_anchors * 4, H, W).
            mlvl_anchors (list[Tensor]): Box reference for each scale level
                with shape (num_total_anchors, 4).
            img_shape (tuple[int]): Shape of the input image,
                (height, width, 3).
            scale_factor (ndarray): Scale factor of the image arange as
                (w_scale, h_scale, w_scale, h_scale).
            cfg (mmcv.Config): Test / postprocessing configuration,
                if None, test_cfg would be used.
            rescale (bool): If True, return boxes in original image space.

        Returns:
            Tensor: Labeled boxes in shape (n, 5), where the first 4 columns
                are bounding box positions (tl_x, tl_y, br_x, br_y) and the
                5-th column is a score between 0 and 1.
        """
        cfg = self.test_cfg if cfg is None else cfg
        # bboxes from different level should be independent during NMS,
        # level_ids are used as labels for batched NMS to separate them
        level_ids = []
        mlvl_scores = []
        mlvl_bbox_preds = []
        mlvl_angle_preds = []
        mlvl_valid_anchors = []
        for idx in range(len(cls_scores)):  # 对每一张特征图
            rpn_cls_score = cls_scores[idx]  # (n_base_anchors*n_classes,H,W)    # rpn应该只有一类
            rpn_bbox_pred = bbox_preds[idx]  # (n_base_anchors*4,H,W)
            rpn_angle_pred = angle_preds[idx]  # (n_base_anchors*onehot_dim,H,W)
            assert rpn_cls_score.size()[-2:] == rpn_bbox_pred.size()[-2:] == rpn_angle_pred.size()[-2:]
            rpn_cls_score = rpn_cls_score.permute(1, 2, 0)  # ->(H,W,n_base_anchors*n_classes)
            if self.use_sigmoid_cls:
                rpn_cls_score = rpn_cls_score.reshape(-1)  # rpn_cls_score:(H*W*base_anchors)
                scores = rpn_cls_score.sigmoid()
            else:
                rpn_cls_score = rpn_cls_score.reshape(-1, 2)
                # we set FG labels to [0, num_class-1] and BG label to
                # num_class in other heads since mmdet v2.0, However we
                # keep BG label as 0 and FG label as 1 in rpn head
                scores = rpn_cls_score.softmax(dim=1)[:, 1]
            rpn_bbox_pred = rpn_bbox_pred.permute(1, 2, 0).reshape(-1, self.reg_dim)  # -> (HWn_base_anchors,4)
            rpn_angle_pred = rpn_angle_pred.permute(1, 2, 0).reshape(-1,
                                                                     self.onehot_dim)  # ->(HWn_base_anchors,onehot_dim)
            anchors = mlvl_anchors[idx]
            if cfg.nms_pre > 0 and scores.shape[0] > cfg.nms_pre:
                # sort is faster than topk
                # _, topk_inds = scores.topk(cfg.nms_pre)
                ranked_scores, rank_inds = scores.sort(descending=True)
                topk_inds = rank_inds[:cfg.nms_pre]
                scores = ranked_scores[:cfg.nms_pre]
                rpn_bbox_pred = rpn_bbox_pred[topk_inds, :]
                rpn_angle_pred = rpn_angle_pred[topk_inds, :]
                anchors = anchors[topk_inds, :]
            mlvl_scores.append(scores)
            mlvl_bbox_preds.append(rpn_bbox_pred)
            mlvl_angle_preds.append(rpn_angle_pred)
            mlvl_valid_anchors.append(anchors)
            level_ids.append(
                scores.new_full((scores.size(0),), idx, dtype=torch.long))

        scores = torch.cat(mlvl_scores)
        anchors = torch.cat(mlvl_valid_anchors)
        rpn_bbox_pred = torch.cat(mlvl_bbox_preds)
        rpn_angle_pred = torch.cat(mlvl_angle_preds)
        proposals = self.bbox_coder.decode(
            anchors, rpn_bbox_pred, rpn_angle_pred, max_shape=img_shape)
        # print(proposals.size())
        ids = torch.cat(level_ids)

        if cfg.min_bbox_size > 0:
            w, h = proposals[:, 2], proposals[:, 3]
            valid_inds = torch.nonzero(
                (w >= cfg.min_bbox_size)
                & (h >= cfg.min_bbox_size),
                as_tuple=False).squeeze()
            if valid_inds.sum().item() != len(proposals):
                proposals = proposals[valid_inds, :]
                scores = scores[valid_inds]
                ids = ids[valid_inds]

        # TODO: remove the hard coded nms type
        hproposals = obb2hbb(proposals)
        nms_cfg = dict(type='nms', iou_thr=cfg.nms_thr)
        _, keep = arb_batched_nms(hproposals, scores, ids, nms_cfg)

        dets = torch.cat([proposals, scores[:, None]], dim=1)
        dets = dets[keep]
        # print(dets[0])
        return dets[:cfg.nms_post]
